﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace GMDViewer
{
	internal class LineBatch : IDisposable
	{
		#region Line struct
		/// <summary>
		/// Struct for a line, instances of this class will be added to lines.
		/// </summary>
		struct Line
		{
			// Positions
			public Vector3 startPoint, endPoint;
			// Colors
			public Color startColor, endColor;

			/// <summary>
			/// Constructor
			/// </summary>
			public Line(
				Vector3 setStartPoint, Color setStartColor,
				Vector3 setEndPoint, Color setEndColor)
			{
				startPoint = setStartPoint;
				startColor = setStartColor;
				endPoint = setEndPoint;
				endColor = setEndColor;
			}

			/// <summary>
			/// Are these two Lines equal?
			/// </summary>
			public static bool operator ==(Line a, Line b)
			{
				return
					a.startPoint == b.startPoint &&
					a.endPoint == b.endPoint &&
					a.startColor == b.startColor &&
					a.endColor == b.endColor;
			}

			/// <summary>
			/// Are these two Lines not equal?
			/// </summary>
			public static bool operator !=(Line a, Line b)
			{
				return
					a.startPoint != b.startPoint ||
					a.endPoint != b.endPoint ||
					a.startColor != b.startColor ||
					a.endColor != b.endColor;
			}

			/// <summary>
			/// Support Equals(.) to keep the compiler happy
			/// (because we used == and !=)
			/// </summary>
			public override bool Equals(object a)
			{
				if (a is Line)
					return (Line)a == this;

				return false;
			}

			/// <summary>
			/// Support GetHashCode() to keep the compiler happy
			/// (because we used == and !=)
			/// </summary>
			public override int GetHashCode()
			{
				return 0; // Not supported or nessescary
			}
		}
		#endregion

		#region Variables
		/// <summary>
		/// Number of lines used this frame, will be set to 0 when rendering.
		/// </summary>
		private int numOfLines = 0;

		/// <summary>
		/// The actual list for all the lines, it will NOT be reseted each
		/// frame like numOfLines! We will remember the last lines and
		/// only change this list when anything changes (new line, old
		/// line missing, changing line data).
		/// When this happens buildVertexBuffer will be set to true.
		/// </summary>
		private List<Line> lines = new List<Line>();

		/// <summary>
		/// Build vertex buffer this frame because the line list was changed?
		/// </summary>
		private bool buildVertexBuffer = false;

		/// <summary>
		/// Vertex buffer for all lines
		/// </summary>
		VertexPositionColor[] lineVertices = new VertexPositionColor[MaxNumOfLines * 2];

		/// <summary>
		/// Real number of primitives currently used.
		/// </summary>
		private int numOfPrimitives = 0;

		/// <summary>
		/// Max. number of lines allowed to prevent to big buffer, will never
		/// be reached, but in case something goes wrong or numOfLines is not
		/// reseted each frame, we won't add unlimited lines (all new lines
		/// will be ignored if this max. number is reached).
		/// </summary>
		protected const int MaxNumOfLines = 16384;

		/// <summary>
		/// Vertex declaration for our lines.
		/// </summary>
		VertexDeclaration decl = null;

		/// <summary>
		/// BasicEffect for drawing lines
		/// </summary>
		BasicEffect effect = null;

		/// <summary>
		/// Cached Device ref
		/// </summary>
		GraphicsDevice m_device = null;

		#endregion

		#region Initialization
		/// <summary>
		/// Init LineManager
		/// </summary>
		public LineBatch(GraphicsDevice device)
		{
			if (device == null)
			{
				throw new ArgumentNullException("XNA device is not initialized, can't init line manager.");
			}
			m_device = device;
			decl = new VertexDeclaration(device, VertexPositionColor.VertexElements);
			effect = new BasicEffect(device, null);
		}
		#endregion

		#region Dispose
		/// <summary>
		/// Dispose
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Dispose
		/// </summary>
		/// <param name="disposing">Disposing</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				decl.Dispose();
			}
		}
		#endregion

		#region AddLine
		/// <summary>
		/// Add line
		/// </summary>
		public void AddLine(
			Vector3 startPoint, Color startColor,
			Vector3 endPoint, Color endColor)
		{
			// Don't add new lines if limit is reached
			if (numOfLines >= MaxNumOfLines)
			{
				/*ignore
				Log.Write("Too many lines requested in LineManager3D. " +
					"Max lines = " + MaxNumOfLines);
				 */
				return;
			}

			// Build line
			Line line = new Line(startPoint, startColor, endPoint, endColor);

			// Check if this exact line exists at the current lines position.
			if (lines.Count > numOfLines)
			{
				if ((Line)lines[numOfLines] != line)
				{
					// overwrite old line, otherwise just increase numOfLines
					lines[numOfLines] = line;
					// Remember to build vertex buffer in Render()
					buildVertexBuffer = true;
				}
			}
			else
			{
				// Then just add new line
				lines.Add(line);
				// Remember to build vertex buffer in Render()
				buildVertexBuffer = true;
			}

			// nextUpValue line
			numOfLines++;
		}

		/// <summary>
		/// Add line (only 1 color for start and end version)
		/// </summary>
		public void AddLine(Vector3 startPoint, Vector3 endPoint,
			Color color)
		{
			AddLine(startPoint, color, endPoint, color);
		}
		#endregion

		#region Update vertex buffer
		protected void UpdateVertexBuffer()
		{
			// Don't do anything if we got no lines.
			if (numOfLines == 0 ||
				// Or if some data is invalid
				lines.Count < numOfLines)
			{
				numOfPrimitives = 0;
				return;
			}

			// Set all lines
			for (int lineNum = 0; lineNum < numOfLines; lineNum++)
			{
				Line line = (Line)lines[lineNum];
				lineVertices[lineNum * 2 + 0] = new VertexPositionColor(
					line.startPoint, line.startColor);
				lineVertices[lineNum * 2 + 1] = new VertexPositionColor(
					line.endPoint, line.endColor);
			}
			numOfPrimitives = numOfLines;

			// Vertex buffer was build
			buildVertexBuffer = false;
		}
		#endregion

		#region Render
		/// <summary>
		/// Render all lines added this frame
		/// </summary>
		public void Render()
		{
			// Need to build vertex buffer?
			if (buildVertexBuffer ||
				numOfPrimitives != numOfLines)
			{
				UpdateVertexBuffer();
			}

			// Render lines if we got any lines to render
			if (numOfPrimitives > 0)
			{
				effect.Begin();
				effect.LightingEnabled = false;
				effect.TextureEnabled = false;
				effect.VertexColorEnabled = true;
				effect.World = Matrix.Identity;
				effect.View = GMDViewer.Singleton.Camera.MatView;
				effect.Projection = GMDViewer.Singleton.Camera.MatProj;

				m_device.VertexDeclaration = decl;

				foreach (EffectPass pass in effect.CurrentTechnique.Passes)
				{
					pass.Begin();
					m_device.DrawUserPrimitives<VertexPositionColor>(
						PrimitiveType.LineList, lineVertices, 0, numOfPrimitives);
					pass.End();
				}

				effect.End();
			}

			// Ok, finally reset numOfLines for next frame
			numOfLines = 0;
		}
		#endregion
	}
}